package com.fll.client;

import com.fll.client.handlers.ClientChannelHandler;
import com.fll.client.handlers.RealServerChannelHandler;
import com.fll.client.listener.ChannelStatusListener;
import com.fll.common.Config;
import com.fll.common.container.Container;
import com.fll.common.container.ContainerHelper;
import com.fll.protocol.IdleCheckHandler;
import com.fll.protocol.ProxyMessage;
import com.fll.protocol.ProxyMessageDecoder;
import com.fll.protocol.ProxyMessageEncoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.SslHandler;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.util.Arrays;

/**
 * @author fanlei
 * @version 1.0
 * @date 2019年01月25日
 */
@Slf4j
public class ProxyClientContainer implements Container, ChannelStatusListener {

    private static final int MAX_FRAME_LENGTH = 1024*1024;
    private static final int LENGTH_FIELD_OFFSET = 0;
    private static final int LENGTH_FIELD_LENGTH = 4;
    private static final int LENGTH_ADJUSTMENT = 0;
    private static final int INITIAL_BYTES_TO_STRIP = 0;

    private NioEventLoopGroup workerGroup = new NioEventLoopGroup();
    private Bootstrap bootstrap;
    private Bootstrap realServerBootstrap = new Bootstrap();
    private Config config = Config.getInstance();
    private SSLContext sslContext;
    private long sleepTimeMill = 1000L;

    public ProxyClientContainer() {
        this.realServerBootstrap.group(this.workerGroup);
        this.realServerBootstrap.channel(NioSocketChannel.class);
        this.realServerBootstrap.handler(new ChannelInitializer<SocketChannel>() {
            public void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new ChannelHandler[]{new RealServerChannelHandler()});
            }
        });
        this.bootstrap = new Bootstrap();
        this.bootstrap.group(this.workerGroup);
        this.bootstrap.channel(NioSocketChannel.class);
        this.bootstrap.handler(new ChannelInitializer<SocketChannel>() {
            public void initChannel(SocketChannel ch) throws Exception {
                if (Config.getInstance().getBooleanValue("ssl.enable", false)) {
                    if (ProxyClientContainer.this.sslContext == null) {
                        ProxyClientContainer.this.sslContext = SslContextCreator.createSSLContext();
                    }

                    ch.pipeline().addLast(new ChannelHandler[]{ProxyClientContainer.this.createSslHandler(ProxyClientContainer.this.sslContext)});
                }

                ch.pipeline().addLast(new ChannelHandler[]{new ProxyMessageDecoder(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH, LENGTH_ADJUSTMENT, INITIAL_BYTES_TO_STRIP)});
                ch.pipeline().addLast(new ChannelHandler[]{new ProxyMessageEncoder()});
                ch.pipeline().addLast(new ChannelHandler[]{new IdleCheckHandler(60, 30, 0)});
                ch.pipeline().addLast(new ChannelHandler[]{new ClientChannelHandler(ProxyClientContainer.this.realServerBootstrap, ProxyClientContainer.this.bootstrap, ProxyClientContainer.this)});
            }
        });
    }

    public void start() {
        this.connectProxyServer();
    }

    private ChannelHandler createSslHandler(SSLContext sslContext) {
        SSLEngine sslEngine = sslContext.createSSLEngine();
        sslEngine.setUseClientMode(true);
        return new SslHandler(sslEngine);
    }

    private void connectProxyServer() {
        this.bootstrap.connect(this.config.getStringValue("server.host"), this.config.getIntValue("server.port")).addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture future) throws Exception {
                if (future.isSuccess()) {
                    ClientChannelMannager.setCmdChannel(future.channel());
                    ProxyMessage proxyMessage = new ProxyMessage();
                    proxyMessage.setType((byte)1);
                    proxyMessage.setUri(ProxyClientContainer.this.config.getStringValue("client.key"));
                    future.channel().writeAndFlush(proxyMessage);
                    ProxyClientContainer.this.sleepTimeMill = 1000L;
                    ProxyClientContainer.log.info("connect proxy server success, {}", future.channel());
                } else {
                    ProxyClientContainer.log.warn("connect proxy server failed", future.cause());
                    ProxyClientContainer.this.reconnectWait();
                    ProxyClientContainer.this.connectProxyServer();
                }

            }
        });
    }

    public void stop() {
        this.workerGroup.shutdownGracefully();
    }

    public void channelInactive(ChannelHandlerContext ctx) {
        this.reconnectWait();
        this.connectProxyServer();
    }

    private void reconnectWait() {
        try {
            if (this.sleepTimeMill > 60000L) {
                this.sleepTimeMill = 1000L;
            }

            synchronized(this) {
                this.sleepTimeMill *= 2L;
                this.wait(this.sleepTimeMill);
            }
        } catch (InterruptedException var4) {
            ;
        }

    }

    public static void main(String[] args) {
        ContainerHelper.start(Arrays.asList(new ProxyClientContainer()));
    }
}